﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using EnvironmentEditor.DataModel;
using EnvironmentEditor.Resources;
using System.IO;

namespace EnvironmentEditor
{
    public partial class MainPage : Form
    {
        private const short CellSize = 20;
        private const short gridLineLength = 3;
        private const int CursorBorderSize = 3;

        private Image image;
        private WorldMap world;

        private Point worldOffset;
        private Point selectedCell;
        private Point editCursor;

        private Brush blackBrush = new SolidBrush(Color.Black);
        private Brush greenBrush = new SolidBrush(Color.Green);
        private Brush darkGreenBrush = new SolidBrush(Color.DarkGreen);

        private Pen grayPen = new Pen(Color.Gray);
        private Pen cursorPen = new Pen(Color.Yellow, 1);
        
        private Rectangle imageRect = new Rectangle(0, 0, 601, 601);

        private ResourceManager resourceManager = new ResourceManager();

        private Dictionary<CellSide, Rectangle> drawOffsets = new Dictionary<CellSide, Rectangle>
        {
            { CellSide.Floor, new Rectangle(0, 0, CellSize-1, CellSize-1)},
            { CellSide.North, new Rectangle(0, -CursorBorderSize, CellSize, CursorBorderSize * 2)},
            { CellSide.South, new Rectangle(0, CellSize - CursorBorderSize, CellSize, CursorBorderSize * 2)},
            { CellSide.West,  new Rectangle(-CursorBorderSize, 0, CursorBorderSize * 2, CellSize)},
            { CellSide.East,  new Rectangle(CellSize-CursorBorderSize, 0, CursorBorderSize * 2, CellSize)}
        };

        private Dictionary<CellSide, Tuple<CellSide, Point>> adjacentSides = new Dictionary<CellSide, Tuple<CellSide, Point>>
        {
            { CellSide.North, new Tuple<CellSide, Point>(CellSide.South, new Point(0, -1))},
            { CellSide.South, new Tuple<CellSide, Point>(CellSide.North, new Point(0, 1))},
            { CellSide.West,  new Tuple<CellSide, Point>(CellSide.East, new Point(-1, 0))},
            { CellSide.East,  new Tuple<CellSide, Point>(CellSide.West, new Point(1, 0))}
        };

        private struct HoverInfo
        {
            public Rectangle rect;
            public CellSide? side;
        }

        public MainPage()
        {
            InitializeComponent();
            this.image = new Bitmap(600, 600);
        }

        private void exitFileMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void MainPage_Load(object sender, EventArgs e)
        {
            this.mapImage.Image = this.image;
            this.world = new WorldMap(30, 30, "Untitled map");

            this.UpdateMap();
        }

        private void newMapMenuItem_Click(object sender, EventArgs e)
        {
            var form = new NewMapForm();
            if (form.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                this.world = new WorldMap((short)form.intMapWidth.Value, (short)form.intMapHeight.Value, form.txtMapName.Text);
                this.worldOffset = Point.Empty;
                this.selectedCell = new Point(1, 1);
                this.UpdateMap();
                this.UpdateCurrentPositionInfo();
            }
        }

        private void UpdateMap()
        {
            Graphics g = Graphics.FromImage(this.image);
            g.FillRectangle(this.blackBrush, this.imageRect);
            
            if (this.world != null)
            {
                this.txtMapName.Text = this.world.Name;
                this.txtWorldSize.Text = string.Format("{0}, {1}", this.world.Width, this.world.Height);

                // draw grid
                Point mapCoord = new Point();
                Size imageSize = new Size(this.imageRect.Width / CellSize + 1, this.imageRect.Height / CellSize + 1);

                for (int y = 0; y < imageSize.Height; y++)
                {
                    for (int x = 0; x < imageSize.Width; x++)
                    {
                        mapCoord.X = x + this.worldOffset.X;
                        mapCoord.Y = y + this.worldOffset.Y;

                        Point c = new Point(x * CellSize, y * CellSize);
                        short left = mapCoord.X != 0 ? gridLineLength : (short)0;
                        short right = mapCoord.X != this.world.Width ? gridLineLength : (short)0;
                        short top = mapCoord.Y != 0 ? gridLineLength : (short)0;
                        short bottom = mapCoord.Y != this.world.Height ? gridLineLength : (short)0;

                        g.DrawLine(this.grayPen, c.X - left, c.Y, c.X + right, c.Y);
                        g.DrawLine(this.grayPen, c.X, c.Y - top, c.X, c.Y + bottom);
                    }
                }

                for (int y = 0; y < imageSize.Height; y++)
                {
                    for (int x = 0; x < imageSize.Width; x++)
                    {
                        mapCoord.X = x + this.worldOffset.X;
                        mapCoord.Y = y + this.worldOffset.Y;

                        var cell = this.world.GetItem((short)mapCoord.X, (short)mapCoord.Y);
                        if (cell != null)
                        {
                            foreach (var sideInfo in drawOffsets)
                            {
                                int wallInfo = cell.GetWall(sideInfo.Key);
                                if (wallInfo == 1)
                                {
                                    Rectangle rect = sideInfo.Value;
                                    rect.Offset(x * CellSize, y * CellSize);
                                    g.FillRectangle(sideInfo.Key == CellSide.Floor ? this.greenBrush : this.darkGreenBrush, rect);
                                }
                            }
                        }
                    }
                }

                Rectangle rctCell = GetRectForCell(new Point(this.selectedCell.X - this.worldOffset.X, this.selectedCell.Y - this.worldOffset.Y));

                // Draw current cell selection
                g.DrawRectangle(this.cursorPen, rctCell);

                // Draw cursor
                HoverInfo? info = GetHoveredRect(rctCell);
                if (info != null)
                {
                    g.DrawRectangle(this.grayPen, info.Value.rect);
                }
            }

            this.mapImage.Invalidate();
        }

        private HoverInfo? GetHoveredRect(Rectangle rctSelectedCell)
        {
            rctSelectedCell.Inflate(CursorBorderSize, CursorBorderSize);
            bool hoversSelectedCell = rctSelectedCell.Contains(this.editCursor);
            if (hoversSelectedCell)
            {
                rctSelectedCell.Inflate(-CursorBorderSize, -CursorBorderSize);
                HoverInfo[] rctSideWalls = CreateHoverInfoForCell(rctSelectedCell);

                return rctSideWalls.FirstOrDefault(x => x.rect.Contains(this.editCursor));
            }
            else
            {
                Point ptHoveredCell = new Point(this.editCursor.X / CellSize, this.editCursor.Y / CellSize);
                return new HoverInfo { rect = new Rectangle(ptHoveredCell.X * CellSize, ptHoveredCell.Y * CellSize, CellSize, CellSize), side = null };
            }
        }

        private static HoverInfo[] CreateHoverInfoForCell(Rectangle rctSelectedCell)
        {
            rctSelectedCell.Inflate(-CursorBorderSize, -CursorBorderSize);
            HoverInfo[] rctSideWalls = new HoverInfo[] {
                new HoverInfo { rect = rctSelectedCell, side = CellSide.Floor }, 
                new HoverInfo { rect = new Rectangle(rctSelectedCell.Left, rctSelectedCell.Top - CursorBorderSize * 2, rctSelectedCell.Width, CursorBorderSize * 2), side = CellSide.North },
                new HoverInfo { rect = new Rectangle(rctSelectedCell.Left, rctSelectedCell.Bottom, rctSelectedCell.Width, CursorBorderSize * 2), side = CellSide.South },
                new HoverInfo { rect = new Rectangle(rctSelectedCell.Left - CursorBorderSize * 2, rctSelectedCell.Top, CursorBorderSize * 2, rctSelectedCell.Height), side = CellSide.West },
                new HoverInfo { rect = new Rectangle(rctSelectedCell.Right, rctSelectedCell.Top, CursorBorderSize * 2, rctSelectedCell.Height), side = CellSide.East }
            };

            return rctSideWalls;
        }

        private static Rectangle GetRectForCell(Point cellOffset)
        {
            return new Rectangle(cellOffset.X * CellSize, cellOffset.Y * CellSize, CellSize, CellSize);
        }

        public void UpdateCurrentPositionInfo()
        {
            if (this.world != null)
            {
                txtCurrentPosition.Text = string.Format("X:{0}, Y:{1}", this.selectedCell.X, this.selectedCell.Y);
                
                var cell = this.world.GetItem((short)this.selectedCell.X, (short)this.selectedCell.Y);
                bool isValid = cell != null;
                if (isValid)
                {
                    txtEastWall.Text = cell.East.ToString();
                    txtNorthWall.Text = cell.North.ToString();
                    txtWestWall.Text = cell.West.ToString();
                    txtSouthWall.Text = cell.South.ToString();
                    txtSky.Text = cell.Sky.ToString();
                    txtFloor.Text = cell.Floor.ToString();
                }

                btnFloor.Enabled = isValid;
            }
        }

        private void mapImage_Click(object sender, EventArgs e)
        {
            if (this.world != null && e is MouseEventArgs)
            {
                var mouseEventArgs = e as MouseEventArgs;

                Rectangle rctCell = new Rectangle((this.selectedCell.X - this.worldOffset.X) * CellSize, (this.selectedCell.Y - this.worldOffset.Y) * CellSize, CellSize, CellSize);
                HoverInfo? info = this.GetHoveredRect(rctCell);

                if (info != null && info.Value.side != null)
                {
                    this.world.GetOrCreateItem(this.selectedCell.X, this.selectedCell.Y).SetWall(info.Value.side.Value, 1);
                    if (this.adjacentSides.ContainsKey(info.Value.side.Value))
                    {
                        var adjacentInfo = this.adjacentSides[info.Value.side.Value];
                        Point adjacentCellCoordinates = this.selectedCell;
                        adjacentCellCoordinates.Offset(adjacentInfo.Item2);
                        this.world.GetOrCreateItem(adjacentCellCoordinates.X, adjacentCellCoordinates.Y).SetWall(adjacentInfo.Item1, 1);
                    }
                }
                else
                {
                    this.selectedCell = new Point(mouseEventArgs.X / CellSize + this.worldOffset.X, mouseEventArgs.Y / CellSize + this.worldOffset.Y);
                    this.UpdateCurrentPositionInfo();
                }

                this.UpdateMap();
            }
        }

        private void btnFloor_Click(object sender, EventArgs e)
        {
            var form = new ChangeTextureForm(this.resourceManager);
            if (form.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                if (this.world != null)
                {
                    var cell = this.world.GetItem((short)this.selectedCell.X, (short)this.selectedCell.Y);
                    if (cell != null)
                    {
                    }
                }
            }
        }

        private void loadToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var openFileDlg = new OpenFileDialog();
            if (openFileDlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                var nameDlg = new TextureNameForm(this.resourceManager);
                if (nameDlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    this.resourceManager.LoadTexture(openFileDlg.FileName, nameDlg.txtName.Text);
                }
            }
        }

        private void mapImage_MouseMove(object sender, MouseEventArgs e)
        {
            this.editCursor = new Point(e.X, e.Y);
            this.UpdateCurrentPositionInfo();
            this.UpdateMap();
        }

        private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog();
            if (saveFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                using (var stream = saveFileDialog.OpenFile())
                using (var writer = new StreamWriter(stream))
                {
                    this.world.Save(writer);   
                }

                MessageBox.Show("File has been saved.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        private void loadToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                using (var stream = openFileDialog.OpenFile())
                using (var reader = new StreamReader(stream))
                {
                    this.world.Load(reader);
                }

                this.UpdateCurrentPositionInfo();
                this.UpdateMap();
            }
        }
    }
}
